package kMesh;
import kGeom.*;


public class kMesh extends kElement {

	public kPointCloud verts; // a 1d array of kGeom verts

	/**
	 * an index table of edges. each cell lists indices from verts
	 */
	public IndexTable edges;

	/**
	 * an index table of faces. each cell lists indices from edges
	 */
	public IndexTable faces;

	/**
	 * an index table indicating relationship between vertices and edges 
	 * each cell corresponds to a vert each cell lists indices from edges
	 */
	public IndexTable vert_edge;

	/**
	 * an index table indicating relationship between edges and faces each 
	 * cell corresponds to a edge each cell lists indices from faces
	 */
	public IndexTable edge_face;

	/*
	 * CONSTRUCTORS
	 * 
	 * 
	 * 
	 */

	public kMesh() {
		testPopulateTwo();
	}

	public void testPopulateOne() {
		// creates a stupid 2d mesh
		// seems to be wrong somehow
		verts = new kPointCloud();
		verts.append(new kVec(121f, 242f));
		verts.append(new kVec(300f, 229f));
		verts.append( new kVec(224f, 147f));
		verts.append( new kVec(320f, 480f));
		verts.append( new kVec(175f, 330f));
		verts.append( new kVec(102f, 127f));

		edges = new IndexTable();
		edges.append(new int[] { 1, 3 });
		edges.append(new int[] { 0, 1 });
		edges.append(new int[] { 0, 5 });
		edges.append(new int[] { 5, 4 });
		edges.append(new int[] { 4, 3 });
		edges.append(new int[] { 3, 2 });
		edges.append(new int[] { 2, 4 });
		edges.append(new int[] { 2, 5 });
		edges.append(new int[] { 2, 0 });
		edges.append(new int[] { 2, 1 });

		faces = new IndexTable();
		faces.append(new int[] { 0, 9, 5 });
		faces.append(new int[] { 9, 1, 8 });
		faces.append(new int[] { 8, 2, 7 });
		faces.append(new int[] { 7, 6, 3 });
		faces.append(new int[] { 6, 4, 5 });

		vert_edge = new IndexTable();
		vert_edge.append(new int[] { 1, 8, 2 });
		vert_edge.append(new int[] { 1, 9, 0 });
		vert_edge.append(new int[] { 5, 6, 7, 8, 9 });
		vert_edge.append(new int[] { 0, 5, 4 });
		vert_edge.append(new int[] { 4, 6, 3 });
		vert_edge.append(new int[] { 3, 7, 2 });

		edge_face = new IndexTable();
		edge_face.append(new int[] { 0 });
		edge_face.append(new int[] { 1 });
		edge_face.append(new int[] { 2 });
		edge_face.append(new int[] { 3 });
		edge_face.append(new int[] { 4 });
		edge_face.append(new int[] { 4, 0 });
		edge_face.append(new int[] { 3, 4 });
		edge_face.append(new int[] { 2, 3 });
		edge_face.append(new int[] { 1, 2 });
		edge_face.append(new int[] { 0, 1 });
	}

	public void testPopulateTwo() {
		// creates a 3x3 grid of points, a 2x2 grid of faces
		verts = new kPointCloud();
		verts.append( new kVec(100, 100));
		verts.append( new kVec(200, 100));
		verts.append( new kVec(300, 100));
		verts.append( new kVec(100, 200));
		verts.append( new kVec(200, 200));
		verts.append( new kVec(300, 200));
		verts.append( new kVec(100, 300));
		verts.append( new kVec(200, 300));
		verts.append( new kVec(300, 300));

		edges = new IndexTable();
		edges.append(new int[] { 0, 3 });
		edges.append(new int[] { 0, 1 });
		edges.append(new int[] { 1, 2 });
		edges.append(new int[] { 2, 5 });
		edges.append(new int[] { 5, 8 });
		edges.append(new int[] { 8, 7 });
		edges.append(new int[] { 7, 6 });
		edges.append(new int[] { 6, 3 });
		edges.append(new int[] { 3, 4 });
		edges.append(new int[] { 4, 1 });
		edges.append(new int[] { 5, 4 });
		edges.append(new int[] { 7, 4 });

		faces = new IndexTable();
		faces.append(new int[] { 0, 1, 8, 9 });
		faces.append(new int[] { 2, 3, 10, 9 });
		faces.append(new int[] { 4, 5, 11, 10 });
		faces.append(new int[] { 6, 7, 8, 11 });

		vert_edge = new IndexTable();
		vert_edge.append(new int[] { 1, 0 });
		vert_edge.append(new int[] { 1, 2, 9 });
		vert_edge.append(new int[] { 2, 3 });
		vert_edge.append(new int[] { 0, 7, 8 });
		vert_edge.append(new int[] { 8, 9, 10, 11 });
		vert_edge.append(new int[] { 3, 4, 10 });
		vert_edge.append(new int[] { 6, 7 });
		vert_edge.append(new int[] { 5, 6, 11 });
		vert_edge.append(new int[] { 4, 5 });

		edge_face = new IndexTable();
		edge_face.append(new int[] { 0 });
		edge_face.append(new int[] { 0 });
		edge_face.append(new int[] { 1 });
		edge_face.append(new int[] { 1 });
		edge_face.append(new int[] { 2 });
		edge_face.append(new int[] { 2 });
		edge_face.append(new int[] { 3 });
		edge_face.append(new int[] { 3 });
		edge_face.append(new int[] { 0, 3 });
		edge_face.append(new int[] { 0, 1 });
		edge_face.append(new int[] { 1, 2 });
		edge_face.append(new int[] { 2, 3 });
	}

	/*
	 * PUBLIC GEOMETRY MANIPULATION
	 * 
	 * 
	 * 
	 */

	void addFace(kVec[] vertArray) {
		// TODO: build function addFace()
		/*
		 * int[] vertIndex = new int[3]; // loop through each point in the array
		 * for (int i = 0; i < vertArray.length; i++) { // check to see if the
		 * point is a duplicate vertIndex[i] = isPointInMesh(vertArray[i]); //
		 * if it isn't a duplicate then add it to the array if (vertIndex[i] <
		 * 0) { verts = kVecArrAppend(verts, vertArray[i]); vertIndex[i] =
		 * verts.length - 1; } }
		 * 
		 * int[] edgeIndex = new int[3]; int[] newEdge = new int[1]; // if the
		 * edges dont exist then add the edges // loop through each point in the
		 * array for (int i = 0; i < vertArray.length; i++) { // check to see if
		 * the point is a duplicate - if last member of the // array - it closes
		 * the face if (i < vertArray.length - 1) { newEdge = new int[] {
		 * vertIndex[i], vertIndex[i + 1] }; } else { newEdge = new int[] {
		 * vertIndex[i], vertIndex[0] }; }
		 * 
		 * edgeIndex[i] = isEdgeInMesh(newEdge); // if it isn't a duplicate then
		 * add it to the array if (edgeIndex[i] < 0) { // edges =
		 * intArr2dAppend(edges, newEdge); // need new function edgeIndex[i] =
		 * edges.length; } } // if the face doesn't exist then add the face -
		 * CURENTLY THIS DOESN'T // CHECK FOR REVERSED DIRECTION if
		 * (isFaceInMesh(edgeIndex) == false) { // add the face to the array //
		 * faces = intArrAppend(faces, edgeIndex); // need new function }
		 */
	}

	/*
	 * PARENTAGE GETS
	 * 
	 * 
	 * 
	 */

	int[] getEdgesOfFace(int index) {
		return faces.getAt(index);
	}

	/**
	 * @param index
	 * index of a mesh face
	 * @return
	 * an array of indices referring to mesh vertices which make up this face.
	 * vertices will be ordered to ensure boundary consistancy, starting from the start point of the first
	 * edge of this face. 
	 */
	public int[] getVertsOfFace(int index) {
		// TODO: be sure points are returned going in a consistant direction
		// TODO: try to do without this function internally
		IndexList edgesOnFace = new IndexList(faces.getAt(index));
		IndexList vertList = new IndexList();
		
		// append first two points from first edge, and remember this second point index
		vertList.append(edges.getAt(edgesOnFace.getAt(0),0));
		vertList.append(edges.getAt(edgesOnFace.getAt(0),1));
		int match = edges.getAt(edgesOnFace.getAt(0),1);
		edgesOnFace.removeAt(0); // pop off this first edge
		
		int n = 0;
		while (n < edgesOnFace.count()) {
			if (edges.getAt(edgesOnFace.getAt(n),0) == match  ){
				// this edge has the vert we're looking for as a startpoint
			} else if (edges.getAt(edgesOnFace.getAt(n),1) == match  ){
				// this edge has the vert we're looking for as an endpoint
			}
			n++;
		}
		
		/*
		for (int i = 0; i < edgesOnFace.length; i++) {
			vertList.append(edges.getAt(edgesOnFace[i],0));
			vertList.append(edges.getAt(edgesOnFace[i],1));
		}
		*/
		//TODO: remove duplicates
		return vertList.toArray();
	}

	int[] getVertsOfEdge(int index) {
		// TODO: try to do without this function internally
		return edges.getAt(index);
	}

	int[] getEdgesThatUseVert(int index) {
		// TODO: try to do without this function internally
		return vert_edge.getAt(index);
	}

	boolean getFacesThatUseVert(int index) {
		// TODO: try to do without this function internally
		// TODO: build function getFacesThatUseVert()
		// // get edges
		// int[] arrEdges = vert_edge[index];
		// int[] arrFaces;
		// // loop through the edges
		// for (int i = 0; i < arrEdges.length; i++) {
		// // arrFaces = edge_face[arrEdges[i]];
		// }
		// // remove duplicates + flatten ????????????????????????
		return false;
	}

	int[] getFacesThatUseEdge(int index) {
		return edge_face.getAt(index);
	}

	/*
	 * ASSOCIATIVE GETS
	 * 
	 * 
	 * 
	 */

	int[] getAdjacentVertToGivenVert(int index) {
		//TODO: build function getAdjacentVertToGivenVert()
		/*
		 * TODO: // get edges int[] arrEdges = edges[index]; int[] arrVerts; //
		 * loop through the edges and get verts for (int i = 0; i <
		 * arrEdges.length; i++){ arrVerts[i] = getVertsOfEdge(arrEdges[i]); } //
		 * remove duplicates + flatten ???????????????????????? return arrVerts;
		 */
		return new int[0];
	}

	int[] getAdjacentEdgesToGivenEdge(int index) {
		//TODO: build function getAdjacentEdgesToGivenEdge()
		/*
		 * TODO: // get verts of edge int[] arrVerts = edges(index); int[]
		 * arrEdges; // get edges of verts arrEdges(0) = vert_edge(arrVerts(0));
		 * arrEdges(1) = vert_edge(arrVerts(1)); // remove duplicates + flatten
		 * ???????????????????????? return arrEdges;
		 */
		return new int[0];
	}

	int[] getAdjacentFacesToGivenFace(int index) {
		//TODO: build function getAdjacentFacesToGivenFace()
		/*
		 * TODO: // get edges int[] arrEdges = faces(index); int[] arrFaces; //
		 * loop through edges to get faces for (int i = 0; i < arrEdges.length;
		 * i++){ arrFaces(i) = edge_face(index); } // remove duplicates +
		 * flatten ???????????????????????? return arrFaces;
		 */
		return new int[0];
	}

	/*
	 * PUBLIC UTILITY
	 * 
	 * 
	 * 
	 */

	// check to see if a point is in the mesh
	int isPointInMesh(kVec newPoint) {
		// loop through points and see if there are duplicates
		int newPointIndex = -1;
		for (int i = 0; i < verts.count(); i++) {
			if (newPoint.isIdentical(verts.getAt(i))) {
				// record the existing index
				newPointIndex = i;
			}
		}
		return newPointIndex;
	}

	// check to see if a face is in the mesh
	boolean isFaceInMesh(int[] newFace) {
		//TODO: FIX THIS!! function will not work b/c list of edges in faces table is not ordered
		// loop through points and see if there are duplicates
		boolean exists = false;
		for (int i = 0; i < faces.count(); i++) {
			if (newFace[0] == faces.getAt(i, 0)
					&& newFace[1] == faces.getAt(i, 1)
					&& newFace[2] == faces.getAt(i, 2)) {
				// record the existing index
				exists = true;
			}
		}
		return exists;
	}

	/*
	 * OUTPUT
	 * 
	 * 
	 * 
	 */

	public void printToConsole() {
		System.out.println("----------------------------------------");
		System.out.println("vertCount:" + verts.count());
		System.out.println("edgeCount:" + edges.count());
		System.out.println("faceCount:" + faces.count());
		System.out.println("---------------------");
		System.out.println("EDGES");
		System.out.println(edges.toString());
		System.out.println("---------------------");
		System.out.println("FACES");
		System.out.println(faces.toString());
		System.out.println("---------------------");
		System.out.println("VERT_EDGE");
		System.out.println(vert_edge.toString());
		System.out.println("---------------------");
		System.out.println("EDGE_FACE");
		System.out.println(edge_face.toString());
	}

	/*
	 * PRIVATE
	 * 
	 * 
	 * 
	 */

	public boolean deleteVerts(IndexList dVerts) {
		// calling this also deletes any edges or faces using these verts
		// also deletes any edges left naked
		// also deletes any verts left naked
		
		IndexList nakedVerts = new IndexList();
		IndexList nakedEdges = new IndexList();
		// TODO, this may need to go inside edge loop!

		for (int v = 0; v < dVerts.count(); v++) {
			// EDGES
			IndexList dEdges = new IndexList(vert_edge.getAt(v));
			for (int e = 0; e < dEdges.count(); e++) {
				// remove refs to this edge and collect indices of edges
				// made naked by their deletion
				nakedVerts.append(removeEdgeRefs(dEdges.getAt(e), new IndexList() ));

				// FACES
				IndexList dFaces = new IndexList(edge_face.getAt(e));
				for (int f = 0; f < dFaces.count(); f++) {
					// remove refs to this face and collect indices of edges
					// made naked by their deletion
					nakedEdges.append(removeFaceRefs(dFaces.getAt(f), dEdges ));
				}
				for (int f = 0; f < dFaces.count(); f++) {
					// delete this face. same as in deleteFaces() method
					faces.remove(dFaces.getAt(f));
					dFaces.reindexDown(dFaces.getAt(f));
				}
			}

			// TODO: do something about nakedEdges created by face deletion

			// remove edge from edge array
			for (int e = 0; e < dEdges.count(); e++) {
				edges.remove(dEdges.getAt(e));
				dEdges.reindexDown(dEdges.getAt(e));
			}

			// END FACES
		}

		// TODO: do something about nakedVerts created by edge deletion

		// REMOVE VERT
		for (int v = 0; v < dVerts.count(); v++) {
			System.out.println("deleteVert(" + v + ")");
			// remove verts from vert array and indexDown
			verts.removeAt(dVerts.getAt(v));
			dVerts.reindexDown(dVerts.getAt(v));

			// remove vert from vert_edge table
			vert_edge.remove(dVerts.getAt(v));
		}

		return false;
	}

	public boolean deleteEdges(IndexList dEdges) {
		// calling this also deletes naked verts, as well as any faces using
		// this edge
		// also deletes any edges left naked
		// also deletes any verts left naked
		
		IndexList nakedVerts = new IndexList();
		IndexList nakedEdges = new IndexList();
		for (int e = 0; e < dEdges.count(); e++) {
			// remove refs to this edge and collect indices of edges made
			// naked by its deletion
			nakedVerts.append( removeEdgeRefs(dEdges.getAt(e), new IndexList() ) );

			// FACES
			IndexList dFaces = new IndexList(edge_face.getAt(e));
			for (int f = 0; f < dFaces.count(); f++) {
				// remove refs to this face and collect indices of edges made
				// naked by its deletion
				nakedEdges.append( removeFaceRefs(dFaces.getAt(f), dEdges ) );
			}
			for (int f = 0; f < dFaces.count(); f++) {
				// delete this face. same as in deleteFaces() method
				faces.remove(dFaces.getAt(f));
				dFaces.reindexDown(dFaces.getAt(f));
			}
		}

		// REMOVE EDGE
		for (int e = 0; e < dEdges.count(); e++) {
			System.out.println("deleteEdge(" + e + ")");
			// remove edge from edge array and index down
			edges.remove(dEdges.getAt(e));
			dEdges.reindexDown(dEdges.getAt(e));

			// reindex face table
			faces.reindexDown(dEdges.getAt(e));
			// reindex vert_edge table
			vert_edge.reindexDown(dEdges.getAt(e));
			// remove edge from edge_face table
			edge_face.remove(dEdges.getAt(e));
		}

		// delete naked children
		deleteVerts(nakedVerts);

		// TODO: delte siblings made naked by parent deletion!!!
		return true;
	}

	private IndexList removeEdgeRefs(int dEdge, IndexList vertsToIgnore) {
		// VERY PRIVATE
		// utility function, serves deleteEdges, deletePoints
		// removes references to edge without deleting edge itself
		// also, does NOT remove faces which reference this edge
		// returns a list of naked points
		System.out.println("removeEdgeRefs(" + dEdge + ")");

		IndexList nakedVerts = new IndexList();
		int[] uVerts = edges.getAt(dEdge);
		for (int v = 0; v < uVerts.length; v++) {
			// remove refernce to dEdge from this vert_edge table
			vert_edge.removeValueFromCellIfFound(uVerts[v], dEdge);	
			if ((vert_edge.count(uVerts[v]) == 0) && vertsToIgnore.containsValue(uVerts[v]) ){
				// if vert_edge[uVerts[v]] is empty, add uVerts[v] to nakedVerts
				nakedVerts.append(uVerts[v]);
			}
		}

		return nakedVerts;
	}

	public boolean deleteFaces(IndexList dFaces) {
		// calling this also deletes naked edges, and, from there, naked verts
		// also deletes any edges left naked
		// also deletes any verts left naked (thru delete edges command)
		
		IndexList nakedEdges = new IndexList();
		for (int f = 0; f < dFaces.count(); f++) {
			nakedEdges.append( removeFaceRefs(dFaces.getAt(f), new IndexList() )  );
		}

		// REMOVE FACE
		for (int f = 0; f < dFaces.count(); f++) {
			System.out.println("deleteFace(" + f + ")");
			// remove edge from edge array and index down
			faces.remove(dFaces.getAt(f));
			dFaces.reindexDown(dFaces.getAt(f));

			// reindex edge_face table
			edge_face.reindexDown(dFaces.getAt(f));
		}

		// delete naked children
		deleteEdges(nakedEdges);

		return true;
	}

	private IndexList removeFaceRefs(int dFace, IndexList edgesToIgnore) {
		// VERY PRIVATE
		// utility function, serves deleteFaces, deleteEdges, deletePoints
		// removes references to face without deleting face itself
		// returns a list of naked edges
		System.out.println("removeFaceRefs(" + dFace + ")");

		IndexList nakedEdges = new IndexList();
		int[] uEdges = faces.getAt(dFace);
		for (int e = 0; e < uEdges.length; e++) {
			// remove refernce to dFace from edge_face table
			edge_face.removeValueFromCellIfFound(uEdges[e], dFace);
			if ((edge_face.count(uEdges[e]) == 0) && edgesToIgnore.containsValue(uEdges[e]) ){
				// if edge_face[uEdges[e]] is empty, add uEdges[e] to nakedEdges
				nakedEdges.append(uEdges[e]);
			}
		}

		return nakedEdges;
	}


}
